<?php
/**
 * In this File the class '\UF\ArrayHelper' is defined.
 *
 * @category   UniKap-Framework
 * @package    Core
 * @subpackage Helpers
 * @author     Ulf -UniKado- Kadner <ulfikado@gmail.com>
 * @since      2014-03-29 16:17
 * @version    0.1
 */

namespace UF
{

    /**
     * Defines a Helper class with some static methods for array handling.
     *
     * @since  v0.1
     */
    class ArrayHelper
    {

        # <editor-fold defaultstate="collapsed" desc="- - - >   H I D D E N   C O N S T R U C T O R   - - - - - - - - - - - - - - - - -">

        /**
         * @since  v0.1
         */
        private function __construct() { }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   S T A T I C   M E T H O D S   - - - - - - - - - - - - - -">

        /**
         * Exctracts a array from a string, that defines key value pair by <b>XML attribute format</b>.
         * e.g.: <code>foo="10" bar="false" baz=":-)"</code>. It results in a associative array.
         *
         * <code>yes|no|on|off|true|false</code> will be automaticaly converted to a boolean representation.
         *
         * HTML entities are converted to the associated characters.
         *
         * @param  string $attributeStr The XML string to parse
         * @param  bool $lowerKeys Konvert all resulting array keys to lower case? (Defaults to FALSE)
         * @return array
         * @since  v0.1
         */
        public static function ParseAttributes( $attributeStr, $lowerKeys = false )
        {
            $res = array();
            $hits = array();
            if ( ! \preg_match_all( '~(?<=\A|[ \r\n\t])(\w+)=(\'([^\']*)\'|"([^"]+)")~', $attributeStr, $hits ) )
                return $res;
            for ( $i = 0, $j = \count( $hits[0] ); $i < $j; $i++ )
            {
                $key = $lowerKeys ? \strtolower( $hits[1][$i] ) : $hits[1][$i];
                if ( isset( $hits[4][$i] ) && !empty( $hits[4][$i] ) )
                    $res[$key] = \UF\unmask_xml ( $hits[4][$i], true );
                else
                    $res[$key] = \UF\unmask_xml ( $hits[3][$i], true );
                if ( \preg_match('~^(yes|no|on|off|true|false)$~i',$res[$key]) )
                    $res[$key] = \UF\TypeTool::StrToType( $res[$key], \UF\TYPE::PHP_BOOLEAN );
            }
            return $res;
        }

        /**
         * Exctracts a array from a string, that defines key value pair by <b>HTML attribute format</b>.
         * e.g.: <code>foo=10 bar=false baz=":-)"</code>. It results in a associative array.
         *
         * <code>yes|no|on|off|true|false</code> will be automaticaly converted to a boolean representation.
         *
         * HTML entities are converted to the associated characters.
         *
         * @param  string $attributeStr The HTML string to parse
         * @param  bool $lowerKeys Konvert all resulting array keys to lower case? (Defaults to FALSE)
         * @return array
         * @since  v0.1
         */
        public static function ParseHtmlAttributes( $attributeStr, $lowerKeys = false )
        {
            $res = array(); $hits = array();
            if ( ! \preg_match_all(
                '~(?<=\A|[ \r\n\t])(\w+-\w+|\w+)=(\'([^\']*)\'|"([^"]+)"|([A-Za-z0-9_.-]+))~', $attributeStr, $hits ) )
                return $res;
            for ( $i = 0, $j = \count( $hits[0] ); $i < $j; ++$i )
            {
                $key = $lowerKeys ? \strtolower( $hits[1][$i] ) : $hits[1][$i];
                if ( isset( $hits[5][$i] ) && !empty( $hits[5][$i] ) )
                    $res[$key] = \UF\unmask_xml( $hits[5][$i], true );
                elseif ( isset( $hits[4][$i] ) && !empty( $hits[4][$i] ) )
                    $res[$key] = \UF\unmask_xml( $hits[4][$i], true );
                else $res[$key] = \UF\unmask_xml( $hits[3][$i], true );
            }
            $hits = null;
            if ( ! \preg_match_all(
                '~(?<=\A|\s)(multiple|selected|disabled|readonly|checked)(?=\Z|\s)~i', $attributeStr, $hits ) )
                return $res;
            for ( $i = 0, $j = \count( $hits[1] ); $i < $j; ++$i )
            {
                $key = $lowerKeys ? \strtolower( $hits[1][$i] ) : $hits[1][$i];
                $res[$key] = $key;
            }
            return $res;
        }

        /**
         * Returns if the defined array is numeric indicated (Array keys 0-n).
         *
         * @param array $array
         * @return bool
         * @since  v0.1
         * @assert (array('a','B','c','d')) == true
         * @assert (range(1,9)) == true
         * @assert (array(1,9,'A'=>4)) == false
         * @assert (array(1,9,3=>4)) == false
         * @assert (array(1,9,2=>4)) == true
         */
        public static function IsNumericIndexed( array $array )
        {
            $nums = \join( '', \range( 0, \count( $array ) - 1 ) );
            return ( $nums === \join( '', \array_keys( $array ) ) );
        }

        /**
         * Returns, if the array $array exclusive contains only integer values.
         *
         * @param array $array The array to check
         * @return boolean
         */
        public static function IsIntegerArray( array $array )
        {
            foreach ( $array as $value ) {
                if ( !\is_int( $value ) ) return false; }
            return true;
        }

        /**
         * Returns, if the array $array exclusive contains only string values.
         *
         * @param array $array The array to check
         * @return boolean
         */
        public static function IsStringArray( array $array )
        {
            foreach ( $array as $value ) {
                if ( !\is_string( $value ) ) return false; }
            return true;
        }

        /**
         * Creates a XML conform XML element attribute string from a associative array. (1-dim.)
         *
         * @param  array $attributes
         * @return string
         * @since  v0.1
         * @assert (array('foo','a'=>4,'b'=>true,'c'=>'"Hello"')) == 'a="4" b="1" c="&quot;Hello&quot;"'
         */
        public static function CreateAttributes( array $attributes )
        {
            if ( empty( $attributes ) ) return '';
            if ( self::IsNumericIndexed( $attributes ) ) return '';
            $res = array();
            foreach ( $attributes as $k => $v )
            {
                if ( !\preg_match( '~^[A-Za-z_]~', $k ) ) continue;
                if ( \is_bool( $v ) ) $vl = ( $v ? '1' : '0' );
                elseif ( \is_int( $v ) || \is_double( $v ) ||
                         \is_float( $v ) ) $vl = \strval( $v );
                else $vl = \UF\mask_arg ( (string) $v );
                $res[] = \sprintf( '%s="%s"', $k, $vl );
            }
            return ' ' . \join( ' ', $res );
        }

        /**
         * Inserts a new Array element to defined array index position.
         *
         * @param  array $array
         * @param  mixed $element
         * @param  int $index
         * @return array
         * @since  v0.1
         * @assert (array(), 4, 0) == array(4)
         * @assert (array(), 4, 1) == array(4)
         * @assert (array('b','foo'), 4, 0) == array(0,'b','foo')
         * @assert (array('b','foo','bar'), 4, 1) == array('b',4,'foo','bar')
         * @assert (array('b','foo','bar'), 4, 2) == array('b','foo',4,'bar')
         * @assert (array('b','foo','bar'), 4, 3) == array('b','foo','bar',4)
         * @assert (array('b','foo','bar'), 4, 4) == array('b','foo','bar',4)
         */
        public static function Insert( array $array, $element, $index )
        {
            $cnt = \count( $array );
            if ( $index < 0 ) $index = 0;
            elseif ( $index > $cnt ) $index = $cnt;
            if ( $index == $cnt )
            {
                $array[] = $element;
                return $array;
            }
            if ( $index == 0 )
            {
                if ( \is_array( $element ) )
                    $array = \array_merge( array( $element ), $array );
                else $array = \array_merge(
                    array( \rtrim( $element, "\r\n" ) ), $array );
                return $array;
            }
            $tmp = \array_slice( $array, 0, $index );
            $tmp[] = \is_array($element) ? $element : \rtrim($element, "\r\n");
            return \array_merge( $tmp, \array_slice( $array, $index ) );
        }

        /**
         * Deletes a array element at defined array index.
         *
         * @param array $array The Array.
         * @param int $index The index of the element to delete
         * @return array
         * @since  v0.1
         * @assert (array('b','foo','bar'), 0) == array('foo','bar')
         * @assert (array('b','foo','bar'), 1) == array('b','bar')
         * @assert (array('b','foo','bar'), 2) == array('b','foo')
         * @assert (array('b','foo','bar'), 3) == array('b','foo','bar')
         */
        public static function Remove( array $array, $index )
        {
            if ( $index < 0 || $index >= \count( $array ) ) return $array;
            if ( $index == 0 ) return \array_slice( $array, 1 );
            if ( $index + 1 == \count( $array ) )
                return \array_slice( $array, 0, -1 );
            $neu = \array_slice( $array, 0, $index );
            return \array_merge( $neu, \array_slice( $array, $index + 1 ) );
        }

        /**
         * Removes all elements inside the defined range beginning at $indexStart, with the defined count
         * from defined array $array.
         *
         * @param array $array The Array.
         * @param int $indexStart The element at this index is the first removed element.
         * @param int $length Optional length/count of the elements to delete.
         * @return array
         * @since  v0.1
         * @assert (array('b','foo','bar','baz','blub'), 1) == array('b')
         * @assert (array('b','foo','bar','baz','blub'), 2) == array('b','foo')
         * @assert (array('b','foo','bar','baz','blub'), 0, 2) == array('bar','baz','blub')
         * @assert (array('b','foo','bar','baz','blub'), 2, -1) == array('b','foo','blub')
         */
        public static function RemoveRange( array $array, $indexStart, $length=null )
        {
            $cnt = \count( $array );
            if ( $indexStart < 0 ) $indexStart = 0;
            elseif ( $indexStart >= $cnt ) return $array;
            if ( \is_null( $length ) ) $length = $cnt - $indexStart;
            elseif ( $length < 0 )
                $length = $cnt - $indexStart + $length;
            if ( $length <= 0 ) return $array;
            $tmp = \array_splice( $array, $indexStart, $length );
            $tmp = null;
            return $array;
        }

        /**
         * Returns the level depth of the given array.
         *
         * Empty array have a depth of 0. If a empty array is inside a other array, it does not increment the depth!
         *
         * e.g.
         *
         * <code>
         * array(array()) # has a max. depth of 1
         * array(array(1)) # has a max. depth of 2
         * array(2, array(1)) # has a max. depth of 2
         * </code>
         *
         * @param  array $array
         * @return int
         * @since  v0.1
         * @assert (array()) == 0
         * @assert (array(array())) == 1
         * @assert (array(array(),array(1)) == 2
         * @assert (array(2,array(1,array())) == 2
         * @assert (array(2,array(1,array(2))) == 3
         */
        public static function GetMaxDepth( array $array )
        {
            if ( \count( $array ) < 1 ) return 0;
            $c = 1;
            foreach ( $array as $v )
            {
                if ( \is_array( $v ) )
                {
                    $x = 1 + self::GetMaxDepth( $v );
                    if ( $x > $c ) $c = $x;
                }
            }
            return $c;
        }

        /**
         * Returns, if a array use only one depth level.
         *
         * @param  array $array
         * @return boolean
         * @since  v0.1
         */
        public static function IsSingleDepth( array $array )
        {
            if ( \count( $array ) < 1 ) return false;
            foreach ( $array as $v )
            {
                if ( \is_array( $v ) || \is_object( $v ) ) return false;
            }
            return true;
        }

        /**
         * Extracts a range fron defined array.
         *
         * @param  array $array The source array
         * @param  int $startIndex The Index where the extraction should start
         * @param  int $length Optional length/count of the elements to extract. If not defined it extract to the end.
         * @return array
         * @since  v0.1
         * @assert (array(0,1,2,3,4,5), 0) == array(0,1,2,3,4,5)
         * @assert (array(0,1,2,3,4,5), 0, 0) == array()
         * @assert (array(0,1,2,3,4,5), 0, 2) == array(0,1)
         * @assert (array(0,1,2,3,4,5), 1) == array(1,2,3,4,5)
         * @assert (array(0,1,2,3,4,5), 2, -1) == array(2,3,4)
         */
        public static function Extract( array $array, $startIndex, $length=null )
        {
            $count = \count( $array );
            if ( $count < 1 ) return array();
            if ( $startIndex >= $count ) return array();
            if ( $startIndex == $count - 1) return array( $array[$startIndex] );
            if ( !\is_integer( $length ) ) $length = $count - $startIndex;
            if ( $length == 0 ) return array();
            elseif ( $length < 0 ) $length = ($count - $startIndex) + $length;
            if ( $length == $count ) return $array;
            return \array_slice( $array, $startIndex, $length );
        }

        # </editor-fold>

    }

}

